{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# 🐕 Random Dog MCP Server & Client Tutorial\n",
        "\n",
        "*Created by [Brett Sanders](https://brettsanders.com) (with the help of AI)*\n",
        "\n",
        "This notebook demonstrates how to use the Model Context Protocol (MCP) to create a server that fetches random dog images and how to interact with it using both a custom client and AI agents.\n",
        "\n",
        "## 📚 What You'll Learn\n",
        "\n",
        "1. **MCP Basics**: Understanding the Model Context Protocol\n",
        "2. **Server Architecture**: How the random dog MCP server works\n",
        "3. **Client Usage**: Using the custom client wrapper\n",
        "4. **Agent Integration**: Connecting MCP servers with AI agents\n",
        "5. **Practical Examples**: Running live examples with real dog images\n",
        "\n",
        "## 📁 Project Files\n",
        "\n",
        "- `random_dog_server.py`: MCP server that provides dog image tools\n",
        "- `random_dog_client.py`: Client wrapper for easier interaction\n",
        "- `random_dog_tutorial.ipynb`: This tutorial notebook\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🚀 Setup and Imports\n",
        "\n",
        "First, let's import all the necessary libraries and set up our environment.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import asyncio\n",
        "import json\n",
        "from dotenv import load_dotenv\n",
        "from agents import Agent, Runner, trace\n",
        "from agents.mcp import MCPServerStdio\n",
        "from IPython.display import display, Markdown, Image\n",
        "import requests\n",
        "\n",
        "# Load environment variables\n",
        "load_dotenv(override=True)\n",
        "\n",
        "print(\"✅ Setup complete! Ready to work with random dogs! 🐕\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🔧 Understanding the MCP Server\n",
        "\n",
        "Let's first examine our MCP server structure. The server provides two main tools:\n",
        "\n",
        "1. **`get_random_dog()`**: Returns complete dog data including URL and file size\n",
        "2. **`get_dog_image_url()`**: Returns just the image URL\n",
        "\n",
        "### Server Code Overview\n",
        "\n",
        "```python\n",
        "@mcp.tool()\n",
        "async def get_random_dog() -> Dict[str, Any]:\n",
        "    \"\"\"Get a random dog image from the random.dog API.\"\"\"\n",
        "    # Fetches from https://random.dog/woof.json\n",
        "    # Returns: {\"url\": \"...\", \"fileSizeBytes\": 123456, \"message\": \"...\"}\n",
        "```\n",
        "\n",
        "The server uses the FastMCP framework to provide these tools through the Model Context Protocol, making them accessible to AI agents and other clients.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🧪 Testing the MCP Server Directly\n",
        "\n",
        "Let's start by testing our MCP server directly to see what tools are available.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Define MCP server parameters\n",
        "params = {\"command\": \"uv\", \"args\": [\"run\", \"random_dog_server.py\"]}\n",
        "\n",
        "print(\"🔍 Testing MCP Server Directly...\")\n",
        "print(\"=\" * 50)\n",
        "\n",
        "# Test the server\n",
        "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
        "    # List available tools\n",
        "    mcp_tools = await server.list_tools()\n",
        "    print(f\"📋 Available tools: {[tool.name for tool in mcp_tools]}\")\n",
        "    print()\n",
        "    \n",
        "    # Show tool details\n",
        "    for tool in mcp_tools:\n",
        "        print(f\"🔧 {tool.name}: {tool.description}\")\n",
        "    \n",
        "    print(\"\\n\" + \"=\" * 50)\n",
        "    print(\"✅ MCP Server is working correctly!\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🐕 Getting Your First Random Dog\n",
        "\n",
        "Now let's fetch our first random dog image using the MCP server tools.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "print(\"🎲 Fetching a random dog...\")\n",
        "\n",
        "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
        "    # Get complete dog data\n",
        "    dog_response = await server.call_tool(\"get_random_dog\", {})\n",
        "    print(f\"🐕 Raw MCP response: {dog_response}\")\n",
        "    \n",
        "    # Get just the URL\n",
        "    url_response = await server.call_tool(\"get_dog_image_url\", {})\n",
        "    print(f\"🔗 Raw URL response: {url_response}\")\n",
        "    \n",
        "    # Parse the complete dog data response\n",
        "    try:\n",
        "        if dog_response and dog_response.content and len(dog_response.content) > 0:\n",
        "            json_text = dog_response.content[0].text\n",
        "            dog_data = json.loads(json_text)\n",
        "            \n",
        "            print(f\"\\n📊 Parsed Dog Data: {dog_data}\")\n",
        "            \n",
        "            if 'url' in dog_data:\n",
        "                url = dog_data['url']\n",
        "                file_size = dog_data.get('fileSizeBytes', 'Unknown')\n",
        "                \n",
        "                print(f\"\\n📊 Dog Image Details:\")\n",
        "                print(f\"   URL: {url}\")\n",
        "                print(f\"   File Size: {file_size} bytes\")\n",
        "                \n",
        "                # Store the URL for the next cell\n",
        "                latest_dog_url = url\n",
        "                latest_dog_size = file_size\n",
        "    except Exception as e:\n",
        "        print(f\"❌ Error parsing dog data: {e}\")\n",
        "    \n",
        "    # Parse the URL-only response\n",
        "    try:\n",
        "        if url_response and url_response.content and len(url_response.content) > 0:\n",
        "            url_text = url_response.content[0].text\n",
        "            print(f\"\\n🔗 Parsed URL: {url_text}\")\n",
        "    except Exception as e:\n",
        "        print(f\"❌ Error parsing URL response: {e}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🖼️ Displaying the Dog Image\n",
        "\n",
        "Let's actually display the dog image in our notebook!\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Get a fresh dog image for display\n",
        "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
        "    dog_response = await server.call_tool(\"get_random_dog\", {})\n",
        "\n",
        "# Parse the MCP response - the actual data is in content[0].text as JSON\n",
        "try:\n",
        "    if dog_response and dog_response.content and len(dog_response.content) > 0:\n",
        "        # Extract the JSON string from the response\n",
        "        json_text = dog_response.content[0].text\n",
        "        dog_data = json.loads(json_text)\n",
        "        \n",
        "        if 'url' in dog_data and dog_data['url']:\n",
        "            url = dog_data['url']\n",
        "            file_size = dog_data.get('fileSizeBytes', 'Unknown')\n",
        "            \n",
        "            print(f\"🐕 Here's your random dog!\")\n",
        "            print(f\"📊 File size: {file_size} bytes\")\n",
        "            print(f\"🔗 URL: {url}\")\n",
        "            print()\n",
        "            \n",
        "            try:\n",
        "                # Display the image\n",
        "                display(Image(url=url, width=400))\n",
        "            except Exception as e:\n",
        "                print(f\"❌ Could not display image: {e}\")\n",
        "                print(f\"But you can view it at: {url}\")\n",
        "        else:\n",
        "            print(f\"❌ No valid URL in response: {dog_data}\")\n",
        "    else:\n",
        "        print(f\"❌ Invalid response format: {dog_response}\")\n",
        "except json.JSONDecodeError as e:\n",
        "    print(f\"❌ Error parsing JSON response: {e}\")\n",
        "    print(f\"Raw response: {dog_response}\")\n",
        "except Exception as e:\n",
        "    print(f\"❌ Error processing response: {e}\")\n",
        "    print(f\"Response: {dog_response}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🎯 Using the Custom Client\n",
        "\n",
        "Now let's use our custom `RandomDogMCPClient` which provides a more convenient interface.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Import our custom client\n",
        "from random_dog_client import RandomDogMCPClient\n",
        "\n",
        "print(\"🎯 Testing Custom RandomDogMCPClient...\")\n",
        "print(\"=\" * 50)\n",
        "\n",
        "async with RandomDogMCPClient() as client:\n",
        "    # List available tools\n",
        "    tools = await client.list_tools()\n",
        "    print(f\"📋 Available tools: {[tool.name for tool in tools]}\")\n",
        "    \n",
        "    # Get random dog data using the client\n",
        "    print(\"\\n🐕 Getting dog data via client...\")\n",
        "    dog_response = await client.get_random_dog()\n",
        "    \n",
        "    # Parse the client response (it also returns MCP format)\n",
        "    try:\n",
        "        if dog_response and dog_response.content and len(dog_response.content) > 0:\n",
        "            json_text = dog_response.content[0].text\n",
        "            dog_info = json.loads(json_text)\n",
        "            print(f\"Dog info: {dog_info}\")\n",
        "    except Exception as e:\n",
        "        print(f\"Dog response: {dog_response}\")\n",
        "    \n",
        "    # Get just the URL using the client\n",
        "    print(\"\\n🔗 Getting dog URL via client...\")\n",
        "    url_response = await client.get_dog_image_url()\n",
        "    try:\n",
        "        if url_response and url_response.content and len(url_response.content) > 0:\n",
        "            dog_url = url_response.content[0].text\n",
        "            print(f\"Dog URL: {dog_url}\")\n",
        "    except Exception as e:\n",
        "        print(f\"URL response: {url_response}\")\n",
        "    \n",
        "    print(\"\\n✅ Custom client working perfectly!\")\n",
        "    print(\"🎯 The client provides a convenient wrapper around the MCP server calls!\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🤖 AI Agent Integration\n",
        "\n",
        "Now for the exciting part - let's create an AI agent that can use our dog tools!\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Create an AI agent that can fetch dog images\n",
        "instructions = \"\"\"You are a helpful and enthusiastic dog-loving assistant! 🐕\n",
        "\n",
        "You have access to tools that can fetch random dog images from the internet. \n",
        "When someone asks for a dog image, use your tools to get one and provide:\n",
        "1. The image URL\n",
        "2. The file size information\n",
        "3. An enthusiastic comment about dogs\n",
        "\n",
        "Always be excited and positive about dogs!\"\"\"\n",
        "\n",
        "request = \"Hi! I'm having a bad day. Can you cheer me up with a cute random dog image?\"\n",
        "model = \"gpt-4o-mini\"\n",
        "\n",
        "print(\"🤖 Creating Dog-Loving AI Agent...\")\n",
        "print(\"=\" * 50)\n",
        "\n",
        "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
        "    # Create the agent with our MCP server\n",
        "    agent = Agent(\n",
        "        name=\"dog_enthusiast\", \n",
        "        instructions=instructions, \n",
        "        model=model, \n",
        "        mcp_servers=[mcp_server]\n",
        "    )\n",
        "    \n",
        "    # Run the agent with tracing\n",
        "    with trace(\"dog_agent_demo\"):\n",
        "        result = await Runner.run(agent, request)\n",
        "        \n",
        "    print(\"🐕 Agent Response:\")\n",
        "    print(\"=\" * 30)\n",
        "    display(Markdown(result.final_output))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 📋 Summary and Next Steps\n",
        "\n",
        "### What We've Learned\n",
        "\n",
        "1. **MCP Server Setup**: How to create tools that fetch external data\n",
        "2. **Client Wrapping**: Building convenient client interfaces  \n",
        "3. **Agent Integration**: Connecting MCP servers with AI agents\n",
        "4. **Error Handling**: Robust error handling for external APIs\n",
        "5. **Practical Applications**: Real-world usage patterns\n",
        "\n",
        "### Key Components\n",
        "\n",
        "- **`random_dog_server.py`**: FastMCP server with dog image tools\n",
        "- **`random_dog_client.py`**: Convenient client wrapper\n",
        "- **Agent Integration**: Seamless AI agent connectivity\n",
        "\n",
        "### Next Steps\n",
        "\n",
        "1. **Extend the Server**: Add more tools (cat images, specific breeds, etc.)\n",
        "2. **Add Caching**: Implement local caching for better performance\n",
        "3. **Error Recovery**: Add retry logic and fallback mechanisms\n",
        "4. **Multi-API Support**: Connect to multiple pet image APIs\n",
        "5. **Advanced Agents**: Create specialized agents for different use cases\n",
        "\n",
        "### 🚀 Ready to Build More?\n",
        "\n",
        "You now have a solid foundation for:\n",
        "- Creating your own MCP servers\n",
        "- Building client wrappers\n",
        "- Integrating with AI agents\n",
        "- Handling external APIs safely\n",
        "\n",
        "Happy coding! 🐕🎉\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🔧 Utility Functions\n",
        "\n",
        "Here are some utility functions you can use in your own projects:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Utility function for simple dog fetching using direct MCP server\n",
        "async def get_simple_dog():\n",
        "    \"\"\"Simple utility to get a random dog with error handling\"\"\"\n",
        "    try:\n",
        "        async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
        "            dog_response = await server.call_tool(\"get_random_dog\", {})\n",
        "            if dog_response and dog_response.content and len(dog_response.content) > 0:\n",
        "                json_text = dog_response.content[0].text\n",
        "                return json.loads(json_text)\n",
        "            else:\n",
        "                return {\"error\": \"Invalid response format\", \"url\": \"\", \"fileSizeBytes\": 0}\n",
        "    except Exception as e:\n",
        "        return {\"error\": str(e), \"url\": \"\", \"fileSizeBytes\": 0}\n",
        "\n",
        "# Test the utility\n",
        "print(\"🔧 Testing utility function...\")\n",
        "simple_dog = await get_simple_dog()\n",
        "print(f\"Simple dog result: {simple_dog}\")\n",
        "\n",
        "# Another utility for batch fetching\n",
        "async def get_multiple_dogs(count=3):\n",
        "    \"\"\"Get multiple random dogs using direct MCP server\"\"\"\n",
        "    dogs = []\n",
        "    try:\n",
        "        async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
        "            for i in range(count):\n",
        "                try:\n",
        "                    dog_response = await server.call_tool(\"get_random_dog\", {})\n",
        "                    if dog_response and dog_response.content and len(dog_response.content) > 0:\n",
        "                        json_text = dog_response.content[0].text\n",
        "                        dog_data = json.loads(json_text)\n",
        "                        dogs.append(dog_data)\n",
        "                    else:\n",
        "                        dogs.append({\"error\": \"Invalid response\"})\n",
        "                except Exception as e:\n",
        "                    dogs.append({\"error\": str(e)})\n",
        "    except Exception as e:\n",
        "        return [{\"error\": f\"Server connection failed: {str(e)}\"}]\n",
        "    return dogs\n",
        "\n",
        "# Test batch fetching\n",
        "print(\"\\n📦 Testing batch utility...\")\n",
        "batch_dogs = await get_multiple_dogs(2)\n",
        "print(f\"Batch result: Got {len(batch_dogs)} dogs\")\n",
        "for i, dog in enumerate(batch_dogs, 1):\n",
        "    if 'url' in dog and dog['url']:\n",
        "        print(f\"   Dog {i}: {dog['url'][:50]}...\")\n",
        "    else:\n",
        "        print(f\"   Dog {i}: Error - {dog.get('error', 'Unknown error')}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## 🔍 Troubleshooting\n",
        "\n",
        "### Common Issues\n",
        "\n",
        "1. **Server Won't Start**\n",
        "   - Make sure `uv` is installed: `pip install uv`\n",
        "   - Check that all dependencies are available\n",
        "   - Verify the server file path is correct\n",
        "\n",
        "2. **No Images Displaying**\n",
        "   - Check your internet connection\n",
        "   - The random.dog API might be temporarily unavailable\n",
        "   - Some images might be in formats that don't display well in notebooks\n",
        "\n",
        "3. **Agent Not Using Tools**\n",
        "   - Verify the MCP server is properly connected\n",
        "   - Check that tools are listed correctly\n",
        "   - Make sure your OpenAI API key is set\n",
        "\n",
        "4. **Import Errors**\n",
        "   - Ensure you're in the correct directory\n",
        "   - Check that all required packages are installed\n",
        "   - Verify Python path includes the current directory\n",
        "\n",
        "### Debug Commands\n",
        "\n",
        "```python\n",
        "# Test basic connectivity\n",
        "import requests\n",
        "response = requests.get(\"https://random.dog/woof.json\")\n",
        "print(response.status_code, response.json())\n",
        "\n",
        "# Test MCP server manually\n",
        "# Run in terminal: uv run random_dog_server.py\n",
        "```\n",
        "\n",
        "### File Structure\n",
        "\n",
        "Make sure your files are organized like this:\n",
        "\n",
        "```\n",
        "random_dog_mcp_server_client/\n",
        "├── random_dog_server.py\n",
        "├── random_dog_client.py\n",
        "└── random_dog_tutorial.ipynb\n",
        "```\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".venv",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.7"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
